package main

import (
	"bufio"
	"bytes"
	"context"
	"crypto/rand"
	"flag"
	"fmt"
	"gitee.com/rocket049/mycrypto"
	"github.com/ipfs/go-log/v2"
	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p/core/crypto"
	dhthost "github.com/libp2p/go-libp2p/core/host"
	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/peer"
	"github.com/libp2p/go-libp2p/core/peerstore"
	"github.com/libp2p/go-libp2p/core/protocol"
	drouting "github.com/libp2p/go-libp2p/p2p/discovery/routing"
	dutil "github.com/libp2p/go-libp2p/p2p/discovery/util"
	libp2pquic "github.com/libp2p/go-libp2p/p2p/transport/quic"
	"github.com/multiformats/go-multiaddr"
	"github.com/nsf/termbox-go"
	"os"
	"p2p/app/p2pshare/globle"
	dht_tool "p2p/app/tools/dht-tool"
	"path/filepath"
	"runtime"
	"strings"
)

var talkStream network.Stream
var denyList = []string{}

var helpMsg = `功能：使用libp2p共享文件和聊天。
启动：
	./p2pshare -name <名字>
Commands:
	find <keyword>  -- 从网络中查找文件，返回搜索结果"p2p-ID:path/to/file"
	get <p2p-ID:path/to/file>  -- 从对方节点下载文件
	search <key>  -- 搜索名字包含key的用户
	whois <名字>  -- 搜索该名字对应的 p2p-ID
	talk <p2p-ID>  -- 和对方建立聊天连接
	say <something>  -- 向talk连接的对方发送聊天信息
	msg <somgthing>  -- 发送公共信息
	deny <p2p-ID>  -- 拒绝接受对方发出的公共信息
	msgto <p2p-ID> <something>  -- 向 p2p-ID 节点发送聊天信息
	本节点的共享文件保存路径为：`

var logger = log.Logger("p2pShare")

func makeHost(port int, privkey crypto.PrivKey) (dhthost.Host, error) {
	// 0.0.0.0 will listen on any interface device.
	sourceMultiAddr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", port))
	if err != nil {
		fmt.Println("err ", err)
		return nil, err
	}
	// libp2p.New constructs a new libp2p Host.
	// Other options can be added here.
	return libp2p.New(
		libp2p.ListenAddrs(sourceMultiAddr),
		libp2p.Transport(libp2pquic.NewTransport),
		libp2p.Identity(privkey),
		libp2p.DefaultSecurity,
	)
}
func readData(rw *bufio.ReadWriter) {
	for {
		str, _ := rw.ReadString('\n')

		if str == "" {
			return
		}
		if str != "\n" {
			// Green console colour: 	\x1b[32m
			// Reset console colour: 	\x1b[0m
			fmt.Printf("\x1b[32m%s\x1b[0m> ", str)
		}
	}
}

func handleStream(stream network.Stream) {
	logger.Info("Got a new stream!")

	// Create a buffer stream for non blocking read and write.
	//rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

	go echoData(stream)

	// 'stream' will stay open until you close it (or the other side closes it).
}

// 简易而实用的多路由，而且使用流传输，大家固定传输的格式，这样路由就不会错了
func echoData(stream network.Stream) {
	rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
	str, err := rw.ReadString('\n')
	if err != nil {
		stream.Close()
		return
	} //else {
	//fmt.Printf("\x1b[32m%s : %s\x1b[0m> ", stream.Conn().RemotePeer().String(), str)
	//}
	args := strings.SplitN(strings.TrimSpace(str), " ", 2)
	switch args[0] {
	case "find":
		res, err := searchfile(sharePath, strings.TrimSpace(args[1]))
		if err != nil {
			panic(err)
		}
		for i := range res {
			rw.WriteString(stream.Conn().LocalPeer().String() + ":" + res[i] + "\n")
		}

	case "get":
		//download file
		err = sendfile(stream, args[1]) // 将流传递下去，并且得到一个文件名
		if err == nil {
			fmt.Println("send successful")
		} else {
			fmt.Println("send fail:", err)
		}

	case "whois":
		if args[1] == Name {
			rw.WriteString(fmt.Sprintf("%s -> %s\n", stream.Conn().LocalPeer().String(), Name))
		}
	case "search":
		if strings.Contains(Name, args[1]) {
			rw.WriteString(fmt.Sprintf("%s -> %s\n", stream.Conn().LocalPeer().String(), Name))
		}
	case "list":
		if len(Name) > 0 {
			rw.WriteString(fmt.Sprintf("%s -> %s\n", stream.Conn().LocalPeer().String(), Name))
		}
	case "msg":
		if containName(denyList, stream.Conn().RemotePeer().String()) == false {
			if runtime.GOOS == "windows" { // 在 Windows 系统上有条件地执行代码
				fmt.Printf("%s : \x1b[32m%s\x1b[0m> \n", stream.Conn().RemotePeer().String(), args[1])
			} else {
				fmt.Printf("\x1b[32m%s : %s\x1b[0m\n> ", stream.Conn().RemotePeer().String(), args[1])
			}

		}

		stream.Close()
	case "talk":
		if talkStream != nil {
			talkStream.Close()
		}
		talkStream = stream
		if runtime.GOOS == "windows" {
			fmt.Printf("talk connected : %s\n> ", args[1])
		} else {
			fmt.Printf("\x1b[32mtalk connected : %s\x1b[0m\n> ", args[1])
		}

		trw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
		go readData(trw)
		return

	default:
		rw.WriteString("Unknown command:" + str)

	}
	rw.Flush()
	err = stream.Close()
	fmt.Println("sender have close the stream")
	if err != nil {
		return
	}
}

func search(stream network.Stream, key string) {

	rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
	rw.WriteString("find " + key + "\n")
	rw.Flush()
	for {
		str, err := rw.ReadString('\n')
		if err != nil {
			//fmt.Println("Error reading from buffer")
			break
		}

		if str == "" {
			break
		}
		if str != "\n" {
			// Green console colour: 	\x1b[32m
			// Reset console colour: 	\x1b[0m
			if runtime.GOOS == "windows" {
				fmt.Printf("%s\n> ", str)
			} else {
				fmt.Printf("\x1b[32m%s\x1b[0m\n> ", str)
			}

		}

	}
}

var Name string

func main() {
	log.SetAllLoggers(log.LevelError)
	log.SetLogLevel("p2pShare", "warn")
	help := flag.Bool("h", false, "Display Help")
	pub := flag.Bool("pub", false, "公开公网地址")
	nocrypt := flag.Bool("nocrypt", false, "是否对私有密钥加密")
	flag.StringVar(&Name, "name", "", "set user name")
	//bootstrap := flag.String("bootstrap", "/ip4/10.12.155.63/udp/59488/quic/p2p/QmRUoiAGmvhwxZ17W52N3XRRLoVfkLRWVafeSoGxDQJGTJ", "bootstrap node address")

	config, err := ParseFlags()
	if err != nil {
		panic(err)
	}
	//不同名字，不同 private key
	keyPath = filepath.Join(keyPath, Name+"_priv.key")

	if *help {
		fmt.Println("This program demonstrates a simple p2p file share application using libp2p")
		flag.PrintDefaults()
		return
	}

	ctx := context.Background()

	// libp2p.New constructs a new libp2p Host. Other options can be added
	// here.
	var port int = 0
	if *pub {
		port = 4005
	}
	privkey := getPrivKey(keyPath, *nocrypt)
	host, err := makeHost(port, privkey)
	if err != nil {
		logger.Warn(err)
		fmt.Println("?test")
		panic(err)
	}
	globle.GlobleHost = &host
	fmt.Println("Host created. We are:", host.ID())

	// host.Addrs()方法可以用于获取本地节点的所有多地址信息。
	for _, addr1 := range host.Addrs() {
		if strings.Contains(addr1.String(), "127.") || strings.Contains(addr1.String(), "::1") {
			continue
		}
		maddr := fmt.Sprintf("%s/p2p/%s", addr1.String(), host.ID().String())
		fmt.Println("Listen:", maddr)
	}

	// Set a function as stream handler. This function is called when a peer
	// initiates a connection and starts a stream with this peer.
	//将函数设置为流处理程序。当对等方

	//启动连接并启动与该对等方的流。
	host.SetStreamHandler(protocol.ID(config.ProtocolID), handleStream)

	// We use a rendezvous point "meet me here" to announce our location.
	// This is like telling your friends to meet you at the Eiffel Tower.
	//我们使用一个集合点“在这里等我”来宣布我们的位置。

	//这就像告诉你的朋友去艾菲尔铁塔见你一样。
	logger.Info("Announcing ourselves...")

	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// Start a DHT, for use in peer discovery. We can't just make a new DHT
	// client because we want each peer to maintain its own local copy of the
	// DHT, so that the bootstrapping node of the DHT can go down without
	// inhibiting future peer discovery.
	//启动一个DHT，用于对等点发现。我们不能只做一个新的DHT

	//客户端，因为我们希望每个对等端维护其自己的

	//DHT，因此DHT的自举节点可以在没有

	//抑制未来的对等体发现。

	//kademliaDHT, err := dht.New(ctx, host, dht.Mode(dht.ModeServer)) // 表示将 DHT 模式设置为服务器模式，允许节点作为 DHT 网络的一部分来存储和查找数据
	//if err != nil {
	//	panic(err)
	//}
	//
	//err = nil
	//// 这一段用来连接引导节点
	//maddrs := []string{*bootstrap}
	var peerinfos []peer.AddrInfo
	//var wg sync.WaitGroup
	//mas, _ := StringsToAddrs(maddrs)
	//
	//for _, peerAddr := range mas {
	//
	//	peerinfo, _ := peer.AddrInfoFromP2pAddr(peerAddr)
	//	host.Peerstore().AddAddrs(peerinfo.ID, peerinfo.Addrs, peerstore.PermanentAddrTTL)
	//	peerinfos = append(peerinfos, *peerinfo)
	//	wg.Add(1)
	//	go func() {
	//		defer wg.Done()
	//		if err := host.Connect(ctx, *peerinfo); err == nil {
	//			fmt.Println("成功连接 bootstrap 节点:", *peerinfo)
	//		}
	//	}()
	//}
	//wg.Wait()
	//// 上面是引导节点的连接
	//
	//// Bootstrap the DHT. In the default configuration, this spawns a Background
	//// thread that will refresh the peer table every five minutes.
	////启动DHT。在默认配置中，这会生成一个背景
	//
	kademliaDHT, err := dht_tool.NewDHTTool(host)
	if err != nil {
		return
	}

	//线程，每五分钟刷新一次对等表。
	logger.Debug("Bootstrapping the DHT")
	if err = kademliaDHT.Bootstrap(ctx); err != nil {
		panic(err)
	}
	routingDiscovery := drouting.NewRoutingDiscovery(kademliaDHT)

	go func() {
		//for {
		//d, err := routingDiscovery.Advertise(ctx, config.RendezvousString)
		//if err != nil {
		//	logger.Info(err.Error())
		//} else {
		//	time.Sleep(d)
		//}

		//}

		dutil.Advertise(ctx, routingDiscovery, config.RendezvousString)
		logger.Debug("Searching for other peers...")
		peerChan, err := routingDiscovery.FindPeers(ctx, config.RendezvousString)
		if err != nil {
			panic(err)
		}

		for peer := range peerChan {
			if peer.ID == host.ID() {
				continue
			}
			logger.Debug("Found peer within dht:", peer)

			if err := host.Connect(ctx, peer); err != nil {
				fmt.Println("Connection failed:", err)
				continue
			}
			peerinfos = append(peerinfos, peer)

		}
	}()

	// mdns 发现节点
	go func() {
		peerChan := initMDNS(host, config.RendezvousString)
		for { // allows multiple peers to join
			peer := <-peerChan // will block untill we discover a peer
			//fmt.Println("Found peer within mdns:", peer)

			if err := host.Connect(ctx, peer); err != nil {
				fmt.Println("Connection failed:", err)
				continue
			}
			peerinfos = append(peerinfos, peer)
		}
	}()

	logger.Info("Successfully announced!")

	if *pub {
		select {}
		return
	}
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	fmt.Println(helpMsg, sharePath)

	stdReader := bufio.NewReader(os.Stdin)

	// 这里是己方的操作
	for {
		fmt.Print("> ")
		sendData, err := stdReader.ReadString('\n') // 阻塞地获取窗口消息
		if err != nil {
			fmt.Println("Error reading from stdin")
			panic(err)
		}
		sendData = strings.TrimSpace(sendData)   // 去除字符串两端的空格
		args := strings.SplitN(sendData, " ", 2) // 根据空格将字符串分成两串
		if len(args) != 2 {
			if strings.TrimSpace(sendData) == "exit" {
				break
			}
			switch strings.TrimSpace(sendData) {
			case "list":
				queryDht(host, routingDiscovery, ctx, &config, sendData+"\n")
				//fmt.Println("listt") 能输出
			default:
				fmt.Println("error command")
			}
			continue
		}
		switch args[0] {
		case "dht":
			dht_tool.HandleInpt(args[1], kademliaDHT, host)
		case "find":
			searchDht(host, routingDiscovery, ctx, &config, args[1])
		case "get":
			vs := strings.SplitN(args[1], ":", 2)
			if len(vs) != 2 {
				fmt.Println("error get command")
				continue
			}
			id1, err := peer.Decode(vs[0])
			if err != nil {
				fmt.Println(vs[0], err.Error())
				continue
			}
			stream1, err := host.NewStream(ctx, id1, protocol.ID(config.ProtocolID))
			if err != nil {
				logger.Warn("connect " + id1.String() + ":" + err.Error())
				continue
			}
			fmt.Println("connected:", vs[0])
			_, err = stream1.Write([]byte("get " + vs[1] + "\n"))
			if err != nil {
				logger.Warn(err.Error())
				stream1.Close()
				continue
			}
			err = recvfile(stream1, filepath.Base(vs[1]))
			if err == nil {
				fmt.Println("recv successful")
				//logger.Info("recv successful")
			} else {
				logger.Warn("recv fail:", err)
			}
		case "talk":
			destination := args[1]
			maddr, err := multiaddr.NewMultiaddr(destination)
			if err != nil {
				logger.Warn(err)
				continue
			}
			// Extract the peer ID from the multiaddr.
			info, err := peer.AddrInfoFromP2pAddr(maddr)
			if err != nil {
				logger.Warn(err)
				continue
			}

			host.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL)

			stream1, err := host.NewStream(ctx, info.ID, protocol.ID(config.ProtocolID))
			if err != nil {
				logger.Warn(err)
				continue
			}
			if talkStream != nil {
				talkStream.Close()
			}
			talkStream = stream1
			tw := bufio.NewWriter(talkStream)
			tw.WriteString(fmt.Sprintf("talk %s\n", stream1.Conn().LocalPeer().String()))
			tw.Flush()
			fmt.Println("talk connected:", args[1])
			trw := bufio.NewReadWriter(bufio.NewReader(stream1), bufio.NewWriter(stream1))
			go readData(trw)
		case "say":
			if talkStream == nil {
				fmt.Println("没有链接聊天对象，请先用 <talk ID> 命令建立连接。")
				continue
			}
			tw := bufio.NewWriter(talkStream)
			tw.WriteString(fmt.Sprintf("%s\n", args[1]))
			tw.Flush()
		case "whois":
			queryDht(host, routingDiscovery, ctx, &config, sendData+"\n")
		case "search":
			queryDht(host, routingDiscovery, ctx, &config, sendData+"\n")
		case "deny":
			if containName(denyList, args[1]) == false {
				denyList = append(denyList, args[1])
			}
		case "msg":
			queryDht(host, routingDiscovery, ctx, &config, fmt.Sprintf("msg (%s)%s\n", Name, args[1]))
		case "msgto":
			msgs := strings.SplitN(args[1], " ", 2)
			if len(msgs) == 1 {
				continue
			}
			msgTo(ctx, host, strings.TrimSpace(msgs[0]), strings.TrimSpace(msgs[1]), &config)

		default:
			fmt.Println("unknown command")
		}
		//end case

	}

}

func containName(v []string, name string) bool {
	for i := range v {
		if v[i] == name {
			return true
		}
	}
	return false
}

func msgTo(ctx context.Context, host dhthost.Host, idStr string, msg string, config *Config) error {
	id1, err := peer.Decode(idStr)
	if err != nil {
		return err
	}
	stream1, err := host.NewStream(ctx, id1, protocol.ID(config.ProtocolID))
	if err != nil {
		return err
	}
	w := bufio.NewWriter(stream1)
	_, err = w.WriteString(fmt.Sprintf("msg (%s)%s\n", Name, msg))
	w.Flush()
	return err
}

func searchDht(host dhthost.Host, routingDiscovery *drouting.RoutingDiscovery, ctx context.Context, config *Config, key string) {
	//logger.Debug("Searching for other peers...")
	peerChan, err := routingDiscovery.FindPeers(ctx, config.RendezvousString)
	if err != nil {
		panic(err)
	}

	for peer1 := range peerChan {
		if peer1.ID == host.ID() {
			continue
		}

		stream, err := host.NewStream(ctx, peer1.ID, protocol.ID(config.ProtocolID))

		if err != nil {

			continue
		} else {

			go search(stream, key)
		}

	}
	fmt.Println("search end:", key)
}

func sendCmdShowEcho(stream network.Stream, cmdstr string) {
	rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
	rw.WriteString(cmdstr)
	rw.Flush()
	//fmt.Println("send:", cmdstr)
	for {
		str, err := rw.ReadString('\n')
		if err != nil {
			break
		} else {
			if runtime.GOOS == "windows" {
				fmt.Printf("%s\n> ", str)
			} else {
				fmt.Printf("\x1b[32m%s\x1b[0m\n> ", str)
			}

		}
	}

}

func queryDht(host dhthost.Host, routingDiscovery *drouting.RoutingDiscovery, ctx context.Context, config *Config, cmdstr string) {
	//logger.Debug("Searching for other peers...")
	peerChan, err := routingDiscovery.FindPeers(ctx, config.RendezvousString)
	if err != nil {
		panic(err)
	}

	for peer1 := range peerChan {
		if peer1.ID == host.ID() {
			continue
		}

		stream, err := host.NewStream(ctx, peer1.ID, protocol.ID(config.ProtocolID))

		if err != nil {
			logger.Warn("Connection failed:", err)
			continue
		} else {

			go sendCmdShowEcho(stream, cmdstr)
		}

		//logger.Info("Connected to:", peer)
	}
	//fmt.Println("query end:", cmdstr)
}

func getPrivKey(filename string, nocrypt bool) (prvkey crypto.PrivKey) {
	crypt := !nocrypt

	if crypt {
		pwd := getPass()
		data, err := mycrypto.CfbDecodeFromFile(filename, pwd)
		if err == nil {
			prvkey, err = crypto.UnmarshalPrivateKey(data)
			if err == nil {
				return
			} else {
				panic("unmarshal priv key:" + err.Error())
			}
		} else if err == mycrypto.ErrPasswordError {
			panic(err)
		}
		//fmt.Println("privkey:", err)
		// Creates a new RSA key pair for this host.
		prvkey, _, err = crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, rand.Reader)
		if err != nil {
			panic(err)
		}

		data, err = crypto.MarshalPrivateKey(prvkey)
		if err != nil {
			panic("marshal priv key:" + err.Error())
		}
		err = mycrypto.CfbEncodeToFile(data, filename, pwd)
		if err != nil {
			panic("save priv key:" + err.Error())
		}
	} else {
		data, err := os.ReadFile(filename)
		if err == nil {
			prvkey, err = crypto.UnmarshalPrivateKey(data)
			if err == nil {
				return
			} else {
				panic("unmarshal priv key:" + err.Error())
			}
		}
		// Creates a new RSA key pair for this host.
		prvkey, _, err = crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, rand.Reader)
		if err != nil {
			panic(err)
		}

		data, err = crypto.MarshalPrivateKey(prvkey)
		if err != nil {
			panic("marshal priv key:" + err.Error())
		}
		err = os.WriteFile(filename, data, 0644)
		if err != nil {
			panic("save priv key:" + err.Error())
		}
	}
	return
}

func getPass() string {
	data := bytes.NewBufferString("")
	termbox.Init()
	defer termbox.Close()
	fmt.Println("输入用户密码：")
	for {
		e := termbox.PollEvent()

		if e.Type == termbox.EventKey {
			if e.Key == 13 {
				break
			}
			data.WriteRune(e.Ch)
		}
	}
	data.WriteByte('\n')
	return data.String()
}
